﻿

//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////

#include "TemporaryDimensionCommons.h"
#include "ILine3d.h"
#include "IDocument.h"
#include "IUiDocument.h"
#include "IGraphicsArc.h"
#include "Vector3dUtils.h"
#include "IGraphicsLine.h"
#include "IGraphicsText.h"
#include "IGraphicsZoomFree.h"
#include "IGraphicsNodeGroup.h"
#include "IUiDocumentViewManager.h"
#include "AlgorithmPositionJudge.h"
#include "TemporaryDimensionDefine.h"
#include "AlgorithmMinimalDistance.h"
#include "ITemporaryDimensionManager.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"

using namespace gcmp;
using namespace Sample;

bool TemporaryDimensionCommons::CalcDockPointsForRectangle(
    const gcmp::Vector3d& firstDockPt, const gcmp::Vector3d& secondDockPt,
    const gcmp::Coordinate3d& coord,
    gcmp::Vector3d& widthStartPt, gcmp::Vector3d& heightStartPt)
{
    Vector3d diagonal = secondDockPt - firstDockPt;
    if (diagonal.IsZero(Constants::LENGTH_EPS))
    {
        return false;
    }
    widthStartPt = firstDockPt + coord.Y()*(coord.Y().Dot(diagonal));
    heightStartPt = firstDockPt + coord.X()*(coord.X().Dot(diagonal));
    return true;
}

bool TemporaryDimensionCommons::CalculateNewPosForViewInput(
    const gcmp::ActionViewInput& inputParam, const std::vector<gcmp::Vector3d>& oPosPts,
    const gcmp::Coordinate3d& coord, gcmp::Vector3d& newPt, std::wstring& notification)
{
    notification.clear();

    switch (inputParam.InputType)
    {
    case ActionViewInputType::Preview_Length:
    case ActionViewInputType::Confirm_Length:
    {
        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"长度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d vecLine(endPt - startPt);
        if (vecLine.IsZero())
        {
            vecLine = Vector3d(100, 0, 0);
        }
        Vector3d dirLine = vecLine;
        dirLine.Normalize();
        newPt = startPt + dirLine * inputParam.InputVal;
        return true;
    }
    case ActionViewInputType::Preview_Angle:
    case ActionViewInputType::Confirm_Angle:
    {
        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"角度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d vecLine(endPt - startPt);
        if (vecLine.IsZero())
        {
            vecLine = Vector3d(100, 0, 0);
        }
        Vector3d dirLine = vecLine;
        double length = dirLine.Normalize();
        Vector3d baseLine = coord.GetX() * 100;
        bool isAlongAntiClockwise = (baseLine.Cross(vecLine).Dot(coord.Z()) > 0);
        double angle = MathUtils::AngleToRadians(inputParam.InputVal)* (isAlongAntiClockwise ? 1 : -1); // 角度转弧度
        Vector3d newLine = Vector3dUtils::RotateVectorAroundAxis(coord.GetX(), coord.GetZ(), angle) * length;
        newPt = startPt + newLine;
        return true;
    }
    case ActionViewInputType::Preview_Radius:
    case ActionViewInputType::Confirm_Radius:
    {
        if (!MathUtils::IsGreaterThan(inputParam.InputVal, 0.0, Constants::LENGTH_EPS))
        {
            notification = GBMP_TR(L"输入的半径值必须大于零。");
            return false;
        }

        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 3, L"半径输入, oPosPts中的点个数, 怎么不是3 ?",L"GDMP",L"2024-03-30");
        Vector3d chordStartPt = oPosPts[0];
        Vector3d chordEndPt = oPosPts[1];
        Vector3d onArcPt = oPosPts[2];

        // 判断三点不共线
        if (Vector3dUtils::IsEqual(chordStartPt, onArcPt) || Vector3dUtils::IsEqual(chordEndPt, onArcPt))
        {
            // 失效条件
            notification = GBMP_TR(L"输入的半径值不合理：三点共线不能做弧。");
            return false;
        }

        auto line12 = ILine3d::Create(chordStartPt, chordEndPt);
        auto line13 = ILine3d::Create(chordStartPt, onArcPt);
        if (!AlgorithmPositionJudge::IsParallel(line12.get(), line13.get()))
        {
            double radius = inputParam.InputVal;
            return GetOnArcPtByChordAndRadius(chordStartPt, chordEndPt,
                coord, radius, onArcPt, newPt, notification);
        }
    }
    case ActionViewInputType::Preview_Fillet:
    case ActionViewInputType::Confirm_Fillet:
    {
        if (!MathUtils::IsGreaterThan(inputParam.InputVal, 0.0, Constants::LENGTH_EPS))
        {
            notification = GBMP_TR(L"输入的倒角圆弧半径值必须大于零。");
            return false;
        }

        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 3, L"圆角弧输入, oPosPts中的点个数, 怎么不是3 ?",L"GDMP",L"2024-03-30");
        Vector3d firstTangentLine = oPosPts[0];
        Vector3d secondTangentLine = oPosPts[1];
        Vector3d intersectPt = oPosPts[2];

        if (Vector3dUtils::IsEqual(firstTangentLine, secondTangentLine))
        {
            // 失效!
            notification = GBMP_TR(L"输入的半径值不合理：三点共线不能做弧。");
            return false;
        }
        double radius = inputParam.InputVal;
        return GetOnArcPtByTangentAndRadius(intersectPt, firstTangentLine, secondTangentLine, coord, radius, newPt, notification);
    }
    case ActionViewInputType::Preview_CentralAngle:
    case ActionViewInputType::Confirm_CentralAngle:
    {
        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 3, L"圆心角输入, oPosPts中的点个数, 怎么不是3 ?",L"GDMP",L"2024-03-30");
        Vector3d centerPt = oPosPts[0];
        Vector3d startPt = oPosPts[1];
        Vector3d endMousePt = oPosPts[2];

        Vector3d dirStart = startPt - centerPt;
        double radius = dirStart.Normalize();
        Vector3d dirEnd = endMousePt - centerPt;
        dirEnd.Normalize();

        bool isAlongAntiClockwise = (dirStart.Cross(dirEnd).Dot(coord.Z()) > 0);
        double centralAng = MathUtils::AngleToRadians(inputParam.InputVal) * (isAlongAntiClockwise ? 1 : -1);

        Vector3d dirNew = Vector3dUtils::RotateVectorAroundAxis(dirStart, coord.Z(), centralAng);
        dirNew.Normalize();

        newPt = centerPt + dirNew * radius;
        return true;
    }
    case ActionViewInputType::Preview_Width:
    case ActionViewInputType::Confirm_Width:
    {
        const ITemporaryDimensionManager *pTemporaryDimensionMgr = ITemporaryDimensionManager::Get();
        DBG_WARN_AND_BREAK_UNLESS(pTemporaryDimensionMgr, L"pTemporaryDimensionMgr is null?",L"GDMP",L"2024-03-30");
        Coordinate3d handlerCoord;
        const ITemporaryDimensionHandler* pTemporaryDimensionHandler = pTemporaryDimensionMgr->GetCurrentTemporaryDimensionHandler();
        bool isSuccess = pTemporaryDimensionHandler->GetCoordinate(handlerCoord);
        if (!isSuccess)
            handlerCoord = coord;

        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"宽度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d startPtLocal = handlerCoord.GetLocalPoint(startPt);
        Vector3d endPtLocal = handlerCoord.GetLocalPoint(endPt);
        Vector3d diagonal(endPtLocal - startPtLocal);
        int widthSign = (diagonal.GetX() > 0.0) ? 1 : -1;

        Vector3d newPtLocal = startPtLocal + Vector3d(widthSign*inputParam.InputVal, diagonal.GetY(), diagonal.GetZ());
        newPt = handlerCoord.GetWorldPoint(newPtLocal);
        return true;
    }
    case ActionViewInputType::Preview_Height:
    case ActionViewInputType::Confirm_Height:
    {
        const ITemporaryDimensionManager *pTemporaryDimensionMgr = ITemporaryDimensionManager::Get();
        DBG_WARN_AND_BREAK_UNLESS(pTemporaryDimensionMgr, L"pTemporaryDimensionMgr is null?",L"GDMP",L"2024-03-30");
        Coordinate3d handlerCoord;
        const ITemporaryDimensionHandler* pTemporaryDimensionHandler = pTemporaryDimensionMgr->GetCurrentTemporaryDimensionHandler();
        bool isSuccess = pTemporaryDimensionHandler->GetCoordinate(handlerCoord);
        if (!isSuccess)
            handlerCoord = coord;

        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"高度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d startPtLocal = handlerCoord.GetLocalPoint(startPt);
        Vector3d endPtLocal = handlerCoord.GetLocalPoint(endPt);
        Vector3d diagonal(endPtLocal - startPtLocal);
        int heightSign = (diagonal.GetY() > 0.0) ? 1 : -1;

        Vector3d newPtLocal = startPtLocal + Vector3d(diagonal.GetX(), heightSign*inputParam.InputVal, diagonal.GetZ());
        newPt = handlerCoord.GetWorldPoint(newPtLocal);
        return true;
    }
    case ActionViewInputType::Preview_TriangleWidth:
    case ActionViewInputType::Confirm_TriangleWidth:
    {
        const ITemporaryDimensionManager *pTemporaryDimensionMgr = ITemporaryDimensionManager::Get();
        DBG_WARN_AND_BREAK_UNLESS(pTemporaryDimensionMgr, L"pTemporaryDimensionMgr is null?",L"GDMP",L"2024-03-30");
        Coordinate3d handlerCoord;
        const ITemporaryDimensionHandler* pTemporaryDimensionHandler = pTemporaryDimensionMgr->GetCurrentTemporaryDimensionHandler();
        bool isSuccess = pTemporaryDimensionHandler->GetCoordinate(handlerCoord);
        if (!isSuccess)
            handlerCoord = coord;
        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"宽度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d startPtLocal = handlerCoord.GetLocalPoint(startPt);
        Vector3d endPtLocal = handlerCoord.GetLocalPoint(endPt);
        Vector3d diagonal(endPtLocal - startPtLocal);
        int widthSign = (diagonal.GetX() > 0.0) ? 1 : -1;

        Vector3d newPtLocal = Vector3d(widthSign*inputParam.InputVal, endPtLocal.GetY(), 0);
        newPt = handlerCoord.GetWorldPoint(newPtLocal);
        return true;

    }
    case ActionViewInputType::Preview_TriangleHeight:
    case ActionViewInputType::Confirm_TriangleHeight:
    {
        const ITemporaryDimensionManager *pTemporaryDimensionMgr = ITemporaryDimensionManager::Get();
        DBG_WARN_AND_BREAK_UNLESS(pTemporaryDimensionMgr, L"pTemporaryDimensionMgr is null?",L"GDMP",L"2024-03-30");
        Coordinate3d handlerCoord;
        const ITemporaryDimensionHandler* pTemporaryDimensionHandler = pTemporaryDimensionMgr->GetCurrentTemporaryDimensionHandler();
        bool isSuccess = pTemporaryDimensionHandler->GetCoordinate(handlerCoord);
        if (!isSuccess)
            handlerCoord = coord;

        DBG_WARN_AND_BREAK_UNLESS(oPosPts.size() == 2, L"高度输入, oPosPts中的点个数, 怎么不是2 ?",L"GDMP",L"2024-03-30");
        Vector3d startPt = oPosPts[0];
        Vector3d endPt = oPosPts[1];
        Vector3d startPtLocal = handlerCoord.GetLocalPoint(startPt);
        Vector3d endPtLocal = handlerCoord.GetLocalPoint(endPt);
        Vector3d diagonal(endPtLocal - startPtLocal);
        int heightSign = (diagonal.GetY() > 0.0) ? 1 : -1;

        Vector3d newPtLocal = Vector3d(endPtLocal.GetX(), heightSign*inputParam.InputVal, 0);
        newPt = handlerCoord.GetWorldPoint(newPtLocal);
        return true;
    }
    default:
    {
        break;
    }
    }

    return false;
}

bool TemporaryDimensionCommons::PreparePosForViewInput(
    const gcmp::ActionViewInput& inputParam,
    const std::vector<gcmp::Vector3d>& pickedPoints, const gcmp::Vector3d& newPickedPoint,
    std::vector<gcmp::Vector3d>& resultPosPts)
{
    int nPickedCount = (int)pickedPoints.size();

    // 检查临时标注定位点
    switch (inputParam.InputType)
    {
    case ActionViewInputType::Confirm_Radius:
    case ActionViewInputType::Preview_Radius:
        DBG_WARN_AND_RETURN_FALSE_UNLESS(pickedPoints.size() >= 2, L"普通半径标注需要提前提供2个定位点！\n按顺序分别是：圆弧起点、圆弧终点",L"GDMP",L"2024-03-30");
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 2));
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 1));
        break;
    case ActionViewInputType::Confirm_Fillet:
    case ActionViewInputType::Preview_Fillet:
        DBG_WARN_AND_RETURN_FALSE_UNLESS(pickedPoints.size() >= 3, L"倒角半径标注需要提前提供3个定位点!\n按顺序分别是：圆弧起点切向向量、圆弧终点切向向量、两者切向交点。",L"GDMP",L"2024-03-30");
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 3));
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 2));
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 1));
        break;
    case ActionViewInputType::Confirm_CentralAngle:
    case ActionViewInputType::Preview_CentralAngle:
        DBG_WARN_AND_RETURN_FALSE_UNLESS(pickedPoints.size() >= 2, L"圆心角标注需要提前提供2个定位点！\n按顺序分别是：圆心（旋转中心）、起始方向上的任意一点。",L"GDMP",L"2024-03-30");
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 2));
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 1));
        break;
    case ActionViewInputType::Confirm_Length:
    case ActionViewInputType::Preview_Length:
    case ActionViewInputType::Confirm_Angle:
    case ActionViewInputType::Preview_Angle:
    case ActionViewInputType::Confirm_Width:
    case ActionViewInputType::Preview_Width:
    case ActionViewInputType::Confirm_Height:
    case ActionViewInputType::Preview_Height:
    case ActionViewInputType::Confirm_TriangleWidth:
    case ActionViewInputType::Preview_TriangleWidth:
    case ActionViewInputType::Confirm_TriangleHeight:
    case ActionViewInputType::Preview_TriangleHeight:
        DBG_WARN_AND_RETURN_FALSE_UNLESS(pickedPoints.size() >= 1, L"长度和矩形宽高标注需要提前提供1个定位点!\n按顺序分别是：起点。",L"GDMP",L"2024-03-30");
        resultPosPts.push_back(pickedPoints.at(nPickedCount - 1));
        break;
    default:
        DBG_WARN(L"输入参数类型非法",L"GDMP",L"2024-03-30");
        return false;
    }

    if (inputParam.InputType == ActionViewInputType::Confirm_Fillet
        || inputParam.InputType == ActionViewInputType::Preview_Fillet)
    {
        // Do Nothing!
    }
    else
    {
        resultPosPts.push_back(newPickedPoint);
    }
    return true;
}

bool TemporaryDimensionCommons::IsInputConfirmed(const gcmp::ActionViewInput& inputParam)
{
    switch (inputParam.InputType)
    {
    case ActionViewInputType::Confirm_Angle:
    case ActionViewInputType::Confirm_CentralAngle:
    case ActionViewInputType::Confirm_Fillet:
    case ActionViewInputType::Confirm_Length:
    case ActionViewInputType::Confirm_Radius:
    case ActionViewInputType::Confirm_Width:
    case ActionViewInputType::Confirm_Height:
    case ActionViewInputType::Confirm_TriangleWidth:
    case ActionViewInputType::Confirm_TriangleHeight:
        return true;
    case ActionViewInputType::Preview_Angle:
    case ActionViewInputType::Preview_CentralAngle:
    case ActionViewInputType::Preview_Fillet:
    case ActionViewInputType::Preview_Length:
    case ActionViewInputType::Preview_Radius:
    case ActionViewInputType::Preview_Width:
    case ActionViewInputType::Preview_Height:
    case ActionViewInputType::Preview_TriangleWidth:
    case ActionViewInputType::Preview_TriangleHeight:
        return false;
    default:
        DBG_WARN(L"未知类型的ActionViewInput!",L"GDMP",L"2024-03-30");
        return false;
    }
}

bool TemporaryDimensionCommons::GetOnArcPtByChordAndRadius(
    const gcmp::Vector3d& chordStart, const gcmp::Vector3d& chordEnd,
    const gcmp::Coordinate3d coord, double radius, const gcmp::Vector3d& mousePos, gcmp::Vector3d& onArcPt, std::wstring& notification)
{
    notification.clear();

    auto chordLine = ILine3d::Create(chordStart, chordEnd);
    Vector3d chordMid = chordLine->GetMiddlePoint();
    double chordLength = chordLine->GetLength();
    if (MathUtils::IsLessThan(radius, chordLength / 2, Constants::LENGTH_EPS))
    {
        notification = GBMP_TR(L"输入的半径值太小，参考最小值为" + StringUtil::ToWString(chordLength / 2, 2));
        return false;
    }
    double centerDistToChord = sqrt(radius * radius - chordLength * chordLength / 4.0); // 此处需要额外判断radius是否合理
    Vector3d onChordPt;
    double dMinDis = 0.0;
    AlgorithmMinimalDistance::Calculate(chordLine.get(), mousePos, onChordPt, false, nullptr, dMinDis);
    bool isMouseOnMinorArc = dMinDis < chordLength / 2;
    Vector3d dirBending = mousePos - onChordPt;
    dirBending.Normalize();

    if (MathUtils::IsEqual(centerDistToChord, 0.0, Constants::LENGTH_EPS))
    {
        // 半圆弧        
        onArcPt = chordMid + dirBending * radius;
        return true;
    }
    else
    {
        // 非半圆弧
        // 需要判断弧的方向
        Vector3d arcCenter = chordMid + dirBending * centerDistToChord * (isMouseOnMinorArc ? -1 : 1);
        OwnerPtr<IGraphicsArc> opArc = IGraphicsArc::Create(arcCenter, chordStart, chordEnd, isMouseOnMinorArc);
        onArcPt = opArc->GetMiddlePoint();
        return true;
    }
}

bool TemporaryDimensionCommons::GetOnArcPtByTangentAndRadius(
    const gcmp::Vector3d& intersectPt, const gcmp::Vector3d& firstTangentLine, const gcmp::Vector3d& secondTangentLine,
    const gcmp::Coordinate3d coord, double radius, gcmp::Vector3d& onArcPt, std::wstring& notification)
{
    notification.clear();

    // 角平分线方向
    Vector3d dirFirstTangentLine = firstTangentLine;
    dirFirstTangentLine.Normalize();
    Vector3d dirSecondTangentLine = secondTangentLine;
    dirSecondTangentLine.Normalize();
    Vector3d dirBisector = dirFirstTangentLine + dirSecondTangentLine;
    if (dirBisector.IsZero(Constants::LENGTH_EPS))
    {
        notification = GBMP_TR(L"输入的值不合理。");
        return false;
    }
    dirBisector.Normalize();
    double distIntersect2Center = radius / fabs(Vector3dUtils::RotateVectorAroundAxis(dirFirstTangentLine, coord.Z(), gcmp::Constants::MATH_PI / 2).Dot(dirBisector));

    // 弧心
    Vector3d arcCenter = intersectPt - dirBisector * distIntersect2Center;

    Vector3d dirCenterToIntersect = intersectPt - arcCenter;
    dirCenterToIntersect.Normalize();

    onArcPt = arcCenter + dirCenterToIntersect * radius;
    return true;
}
